Skip to content

feat(mobile): wire Activity History to GET /my/activity#83

Merged
ahmetabdullahgultekin merged 1 commit into
mainfrom
feat/2026-06-03-activity-history
Jun 3, 2026
Merged

feat(mobile): wire Activity History to GET /my/activity#83
ahmetabdullahgultekin merged 1 commit into
mainfrom
feat/2026-06-03-activity-history

Conversation

@ahmetabdullahgultekin

Copy link
Copy Markdown
Contributor

Summary

The mobile Activity History screen (bottom-nav "History" tab) was permanently empty — it hardcoded val sections = emptyList() and never called the API. This wires it to the existing user-scoped endpoint GET /api/v1/my/activity (the current user's own audit events; the web dashboard already uses it). The admin GET /api/v1/audit-logs path (which 403s for normal users) is left untouched.

Changes

Data layer (shared/commonMain)

  • AuditLogApi / AuditLogApiImpl: add getMyActivity(page, size)GET my/activity?page=&size=, reusing the existing AuditLogPageDto response type. getAuditLogs (admin) unchanged.
  • AuditLogRepository (+Impl): add getMyActivity wrapped in runCatching like the existing method; extracted the shared AuditLogDtoAuditLog mapping into a private toDomain() helper.

Presentation

  • New ActivityHistoryViewModel + ActivityHistoryUiState: loads page 0 (size 50) on init, exposes isLoading / errorMessage / events. Registered in DI via factoryOf(::ActivityHistoryViewModel).
  • ActivityHistoryScreen: replaced the hardcoded empty list with the VM state. Adds a loading spinner and a distinct error state with a Retry button (load errors are no longer silently rendered as an empty list).
  • AppNavigation: injects the VM via koinInject<ActivityHistoryViewModel>().disposeOnLeave() and passes state down — same pattern as the other per-screen ViewModels.

action → chip mapping

AuditLog.action contains Chip
VERIF (e.g. BIOMETRIC_VERIFIED) Verifications
ENROLL (e.g. ENROLLMENT_*, BIOMETRIC_ENROLLED) Enrollments
anything else shown only under All

Entries are grouped by date (YYYY-MM-DD parsed from the ISO timestamp) for the section headers the screen already renders.

i18n

  • New StringKey.ACTHIST_LOAD_ERROR with both enStrings ("Couldn't load your activity history.") and trStrings ("Etkinlik geçmişiniz yüklenemedi.") entries. Reuses existing RETRY, ACTHIST_EMPTY, and chip keys.

Tests

  • FakeAuditLogRepository mock updated with getMyActivity.

Verification

./gradlew :androidApp:compileDebugKotlin --console=plain
BUILD SUCCESSFUL in 40s

Both :shared:compileDebugKotlinAndroid and :androidApp:compileDebugKotlin compile cleanly (only pre-existing deprecation warnings remain).

🤖 Generated with Claude Code

The Activity History screen (bottom-nav "History") was permanently empty —
it hardcoded `val sections = emptyList()` and never called the API.

Wire it to the EXISTING user-scoped endpoint GET /api/v1/my/activity (the
current user's own audit events; no admin authority required). The admin
GET /api/v1/audit-logs path is left untouched (it 403s for normal users).

- AuditLogApi/AuditLogApiImpl: add getMyActivity(page, size) → GET my/activity,
  reusing the existing AuditLogPageDto response type. getAuditLogs (admin)
  unchanged.
- AuditLogRepository(+Impl): add getMyActivity wrapped in runCatching; extract
  the shared AuditLogDto→AuditLog mapping into a toDomain() helper.
- New ActivityHistoryViewModel + ActivityHistoryUiState: loads page 0 (size 50)
  on init, exposes isLoading / errorMessage / events. Registered in DI via
  factoryOf(::ActivityHistoryViewModel).
- ActivityHistoryScreen: replace the hardcoded empty list with the VM state.
  Map each AuditLog.action → chip category: contains "VERIF" → Verifications,
  contains "ENROLL" → Enrollments, everything else shows only under All. Group
  by date (YYYY-MM-DD from the ISO timestamp) for the section headers. Adds a
  loading spinner and a distinct error state with Retry (errors are no longer
  silently rendered as an empty list).
- AppNavigation: inject the VM via koinInject().disposeOnLeave() and pass state,
  matching the existing per-screen ViewModel wiring.
- i18n: new ACTHIST_LOAD_ERROR key with EN + TR; reuse existing RETRY/empty/chip
  keys.
- Update FakeAuditLogRepository test mock with getMyActivity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ahmetabdullahgultekin ahmetabdullahgultekin merged commit fbe7249 into main Jun 3, 2026
3 checks passed
@ahmetabdullahgultekin ahmetabdullahgultekin deleted the feat/2026-06-03-activity-history branch June 3, 2026 08:44
ahmetabdullahgultekin added a commit that referenced this pull request Jun 3, 2026
…ed; scope-B done) (#84)

- CHANGELOG: add the Activity History wiring (#83) under [Unreleased].
- docs/MOBILE_TRIAGE_2026-06-03.md: mark #2 (Activity History), #5 (approve-login)
  FIXED and #7 (cross-device QR) PARTIAL; replace 'Deferred follow-ups' with a
  Resolution section (client-apps #82/#83, web-app #199 merged + deployed) + the
  remaining backend-deferred items.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ahmetabdullahgultekin added a commit that referenced this pull request Jun 3, 2026
…#85)

The operator APK rebuild is done — CI-built release-signed APK from
origin/main (CN=FIVUCSAS, upgrades in place), carrying #82 + #83 to the
device. Adds the signing-via-CI note and a pointer to the parent
USER_FINDINGS_2026-06-03.md sweep.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ahmetabdullahgultekin added a commit that referenced this pull request Jun 3, 2026
…crash)

ActivityHistoryViewModel and AuditLogDashboardViewModel depend on
AuditLogRepository, but #83 wired the ViewModel without registering
AuditLogApi (NetworkModule) or AuditLogRepository (RepositoryModule).
Opening Activity History / the audit-log dashboard on the release build
throws Koin NoDefinitionFoundException -> FATAL crash. Verified on-device.
Adds the two missing single<> bindings (matches the existing pattern).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ahmetabdullahgultekin added a commit that referenced this pull request Jun 3, 2026
…y, NFC, hide Add-card (#86)

* fix(mobile): pre-demo fixes — QR/approve swallowed-401, session expiry, NFC, hide Add-card

Four additive, reversible pre-demo fixes (demo 2026-06-03). Verified each against
origin/main before changing.

#1 + approve-login silent-approve swallow
- QrLoginApiImpl.approveSession and ApproveLoginApiImpl.decide POSTed without
  reading the response, so a non-2xx (e.g. expired bearer → 401/403) was
  swallowed: the phone flipped to APPROVED while the server session stayed
  PENDING and the originating (web) login never advanced. Both now throw a
  ResponseException on a non-success status (status code included in the message
  so ErrorMapper maps it). The 401 path now also triggers the transparent
  refresh+retry below, so a stale-but-refreshable bearer recovers automatically.

#2 session ~15-min logout
- NetworkModule: the refresh-on-401 HttpResponseValidator could not re-fire the
  failed request (its own TODO) → first call after the 15-min access-token
  lifetime surfaced as a logout. Replaced it with an HttpSend interceptor (part
  of ktor-client-core — no new dependency; the project deliberately avoids
  ktor-client-auth, see desktopApp RefreshInterceptor) that refreshes under the
  existing rotation mutex AND retries the original request once with the fresh
  bearer. Refresh logic extracted to a reusable refreshAccessToken() helper.
- TokenManager.isAuthenticated() checked token PRESENCE only; now tolerates an
  expired access token when a refresh token exists (decodes the JWT exp claim via
  base64url + kotlinx.serialization; fail-open for non-JWT tokens), so a
  returning user is not bounced to login when a silent refresh is possible.

#7 NFC won't read (OS detects card, app stays "ready to scan")
- Removed the foreground TECH_DISCOVERED intent-filter + nfc_tech_filter
  meta-data from AndroidManifest so reader mode (enableReaderMode) wins instead
  of competing with the system tag-dispatcher.
- Relaxed AndroidNfcService.processTag: previously dropped a tap unless state was
  exactly WaitingForCard; now only rejects a tap while a read is already in
  flight (Reading), so an Idle/Completed/Error tap starts a fresh read.
- NOTE: NOT verifiable without a physical device — needs on-device `adb logcat`
  confirmation before being trusted. Fully reversible.

#8 "Add card" dead-end button
- Hidden the DashboardScreen "Add card" QuickAction: CardScanScreen is a camera
  photo wizard that never uploads/persists anything. Screen + nav route left
  intact (reversible — uncomment to restore once OCR/upload backend exists).

Tests: +10 (QrApproveLoginStatusCheckTest 6, TokenManagerAuthExpiryTest 4).
Compile: :shared:compileDebugKotlinAndroid + :androidApp:compileDebugKotlin +
processDebugMainManifest all green. Full shared unit suite: 489 tests, 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile,di): register AuditLogApi + AuditLogRepository in Koin (#9 crash)

ActivityHistoryViewModel and AuditLogDashboardViewModel depend on
AuditLogRepository, but #83 wired the ViewModel without registering
AuditLogApi (NetworkModule) or AuditLogRepository (RepositoryModule).
Opening Activity History / the audit-log dashboard on the release build
throws Koin NoDefinitionFoundException -> FATAL crash. Verified on-device.
Adds the two missing single<> bindings (matches the existing pattern).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(mobile): add 'Login requests' home tile (#3)

Surfaces the approve-login approver screen (Screen.ApproveLogin) on the
Dashboard Quick Actions grid (was reachable only via Profile). New
StringKey.DASH_LOGIN_REQUESTS (EN 'Login Requests' / TR 'Giriş İstekleri').
Additive nav tile; opens the already-registered ApproveLoginScreen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(mobile,nfc): clarify NFC reader buttons + friendly passive-auth-unavailable message (#5)

- Relabel the 3 confusingly-similar buttons into an ordered flow (camera MRZ fill / read chip / read any card)
- Show a friendly message for the known NO_TRUST_STORE passive-auth case instead of a raw error
- Clarify YYMMDD date fields

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Ahmet Abdullah Gultekin <rollingcat.help@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ahmetabdullahgultekin added a commit that referenced this pull request Jun 6, 2026
…otes (#89)

- CHANGELOG: promote [Unreleased] → [5.3.1] - 2026-06-06; add the MFA
  transport/IO retry fix (#87, headline) and cross-device/session fixes (#86)
  that had landed post-5.3.0 without changelog entries; keep the #82/#83 items.
- CLAUDE.md: refresh stale "Latest: v5.2.3 / versionCode 10" → "v5.3.1 /
  versionCode 13"; document the CI signed-build command (host has no keystore
  pw); add a "v5.3.1 — MFA stale-connection retry" section.

Co-authored-by: Ahmet Abdullah Gultekin <rollingcat.help@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant